Functions
Higher Order Functions
A function which takes another function as an argument or returns a function is known as a higher order function.
Filter
- Returns a new array with any elements for which the callback function returns
true.
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
Map
- Creates a new array populated with the results of calling a provided function on every element in the calling array.
- The original array does not get altered
const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);
console.log(map1);
// expected output: Array [2, 8, 18, 32]
Foreach
- Executes a callback function on each of the elements in an array in order.
const numbers = [28, 77, 45, 99, 27];
numbers.forEach(number => {
console.log(number);
});
Reduce
- Iterates through an array and returns a single value
const arrayOfNumbers = [1, 2, 3, 4];
const sum = arrayOfNumbers.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
});
- We can pass initial value
const array1 = [1, 2, 3, 4];
// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial);
// expected output: 10
Find
- Returns the first element in the provided array that satisfies the provided testing function.
- If no values satisfy the testing function,
undefinedis returned.
const array1 = [5, 12, 8, 130, 44];
const found = array1.find(element => element > 10);
console.log(found);
// expected output: 12
FindLast
- Returns the value of the last element in an array that satisfies the provided testing function.
- If no elements satisfy the testing function,
undefinedis returned.
const array1 = [5, 12, 50, 130, 44];
const found = array1.findLast((element) => element > 45);
console.log(found);
// expected output: 130
Every
- The
every()method tests whether all elements in the array pass the test implemented by the provided function. - It returns a Boolean value.
const array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(val => val < 40));
// expected output: true
Some
- The
some()method tests whether at least one element in the array passes the test implemented by the provided function. - It returns a Boolean value.
const array = [1, 2, 3, 4, 5];
// checks whether an element is even
const even = (element) => element % 2 === 0;
console.log(array.some(even));
// expected output: true
FindIndex
- Returns the index of the first element in an array that satisfies the provided testing function.
- If no elements satisfy the testing function, -1 is returned.
const array1 = [5, 12, 8, 130, 44];
const isLargeNumber = (element) => element > 13;
console.log(array1.findIndex(isLargeNumber));
// expected output: 3
FindLastIndex
- Returns the index of the last element in an array that satisfies the provided testing function.
- If no elements satisfy the testing function, -1 is returned.
const array1 = [5, 12, 50, 130, 44];
const isLargeNumber = (element) => element > 45;
console.log(array1.findLastIndex(isLargeNumber));
// expected output: 3 (of element with value: 30)
What is Closure In JavaScript?
- Closures - JavaScript | MDN
- A closure gives you access to an outer function's scope from an inner function.
function makeFunc() {
var name = "Mozilla"; // name is a local variable created by init
return function displayName() {
// displayName() is the inner function, that forms the closure
console.log(name); // use variable declared in the parent function
}
}
const res = makeFunc();
res();
- Code explanation
- In some programming languages, the local variables within a function exist for just the duration of that function's execution.
- Once makeFunc() finishes executing, you might expect that the name variable would no longer be accessible. However, because the code still works as expected, this is obviously not the case in JavaScript.
function add(x) {
return function (y) {
return x + y;
};
}
const res = add(14)(14) //res = 14
- Real world example of using closures
JavaScript Currying
What is Currying?
- Currying is a functional programming technique that transforms a function with multiple parameters:
f(a, b, c)
- into a sequence of functions, each accepting a single argument:
f(a)(b)(c)
- Each function call returns another function that waits for the next argument until all required arguments have been provided.
- Currying is useful for:
- Creating specialized functions from generic ones.
- Reusing partially applied functions.
- Building flexible and composable APIs.
- Improving readability in functional programming.
Basic Example
- Curried Function
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c
}
}
}
curriedAdd(1)(2)(3) // 6
- Using Arrow Functions
- Arrow functions make curried functions much more concise.
const add = a => b => c => a + b + c
add(1)(2)(3) // 6
Example 1: Logger Factory
Instead of passing every argument each time, create specialized loggers.
const createLogger = level => addTimestamp => message => {
const time = addTimestamp ? new Date().toISOString() : ''
console.log(
`[${level}]${time ? ' ' + time : ''} ${message}`
)
}
Create specialized loggers:
const info = createLogger('INFO')(true)
const debug = createLogger('DEBUG')(true)
const error = createLogger('ERROR')(true)
Use them:
info('Application started')
// [INFO] 2024-01-15T10:30:00.000Z Application started
debug('Processing request')
// [DEBUG] 2024-01-15T10:30:00.000Z Processing request
error('Connection failed')
// [ERROR] 2024-01-15T10:30:00.000Z Connection failed
Example 2: API Client Factory
Currying is useful for configuring reusable API clients.
const createApiClient = baseUrl => endpoint => options => {
return fetch(`${baseUrl}${endpoint}`, options)
.then(res => res.json())
}
Create a specialized client:
const getGithubUsers =
createApiClient('https://api.github.com')('/users')
Use it:
getGithubUsers({ method: 'GET' })
.then(users => console.log(users))
Function Composition with pipe
- Currying is often used together with function composition.
- A
pipefunction passes the output of one function as the input to the next.
const pipe = (...fns) => x => fns.reduce((value, fn) => fn(value), x)
- Example: transform an API response step by step.
const processApiResponse = pipe(
// Extract data
response => response.data,
// Keep active users
users => users.filter(user => user.isActive),
// Sort alphabetically
users => users.sort((a, b) =>
a.name.localeCompare(b.name)
),
// Convert to display format
users =>
users.map(user => ({
id: user.id,
displayName: `${user.firstName} ${user.lastName}`,
email: user.email
})),
// Return only the first 10 users
users => users.slice(0, 10)
)
Use it:
fetch('/api/users')
.then(res => res.json())
.then(processApiResponse)
.then(users => renderUserList(users))
What Is a Pure Function?
A pure function is a function that follows two simple rules:
- Same input → Same output: Given the same arguments, it always returns the same result.
- No side effects: It doesn't modify anything outside itself, such as its inputs, global variables, or external state.
Pure Function Example
- Since
double()always returns the same output for the same input and doesn't modify anything outside the function, it is pure.
function double(x) {
return x * 2
}
double(5) // 10
double(5) // 10 (always returns the same result)
Impure Function
Different Output for the Same Input
- This function breaks the first rule because it returns a different result each time.
function randomDouble(x) {
return x * Math.random()
}
randomDouble(5) // 2.3456...
randomDouble(5) // 4.1234...
Even though the input is the same, the output changes because Math.random() is non-deterministic.
Side Effects
This function breaks the second rule because it modifies an external variable.
let total = 0
function addToTotal(x) {
total += x
return total
}
addToTotal(5) // 5
addToTotal(5) // 10
The function depends on and modifies external state (total), making it impure.
How to Convert an Impure Function into a Pure Function
- Impure
- This mutates the original object.
function updateEmail(user, email) {
user.email = email
return user
}
- Pure
- Avoid Mutating Objects: Instead of modifying the original object, it returns a new one.
function updateEmail(user, email) {
return {
...user,
email
}
}
- Impure
- This modifies the original array.
function addTodo(todos, newTodo) {
todos.push(newTodo)
return todos
}
- Pure
- Avoid Mutating Arrays: Instead of mutating the existing array, it returns a new array with the added item.
function addTodo(todos, newTodo) {
return [...todos, newTodo]
}
Why Pure Functions Matter
- Easier to Test
- Pure functions are straightforward to test. There's no need for mocks, setup, or cleanup; just call the function and verify the result.
- Easier to Debug
- Pure functions make debugging simpler. If a pure function returns the wrong value, the bug is inside that function. It can't be caused by some other code modifying shared or global state.
- Safe to Cache (Memoization)
- Because pure functions always return the same output for the same input, their results can be safely cached. This optimization technique is called memoization.
- Easier to Parallelize
- Since pure functions don't rely on or modify shared state, they can run independently. This makes them safer and easier to execute concurrently or in parallel.
Composition and Mixins
Instead of relying heavily on inheritance:
Animal
└─ Bird
└─ Duck
You can compose behavior:
Duck
+ Swim
+ Fly
+ Walk
This often produces more flexible code.
Benefits
- Composition over inheritance.
- Reusable behaviors.
- Avoids deep inheritance trees.
- Common in modern JavaScript libraries and frameworks.
Pattern 1: Object Mixins
const Swimmer = {
swim() {
return `${this.name} swims through the water!`
}
}
const Flyer = {
fly() {
return `${this.name} flies through the air!`
}
}
class Duck {
constructor(name) {
this.name = name
}
}
Object.assign(
Duck.prototype,
Swimmer,
Flyer
)
const donald = new Duck("Donald")
donald.swim()
donald.fly()
- Notes
- Methods are added to the prototype.
- All instances share the same methods.
- No inheritance hierarchy required.
Pattern 2: Functional Mixins
A function that accepts a base class and returns an enhanced class.
const withLogging = (Base) =>
class extends Base {
log(message) {
console.log(`[${this.name}] ${message}`)
}
}
const withTimestamp = (Base) =>
class extends Base {
getTimestamp() {
return new Date().toISOString()
}
}
Compose behaviors:
class Demo {
constructor(name) {
this.name = name
}
}
const EnhancedDemo = withTimestamp(withLogging(Duck))
const hero = new EnhancedDemo("Sample")
hero.log("Test")
hero.getTimestamp()